﻿using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PropertyExpression.ControllableQuery;
using PropertyExpression.Common;

namespace Examples
{
    public struct Param<T>
    {
        private readonly T value;

        public Param(T value)
        {
            this.value = value;
        }

        public void AddTo(SqlCommand command, string parameterName)
        {
            throw new NotImplementedException();
        }
    }

    public static class ParamExtensions
    {
        public static Param<T> Param<T>(this T it)
        {
            return new Param<T>(it);
        }

        public static TResult? Select<T, TResult>(this T? it, Func<T, TResult> selector) 
            where T: struct where TResult:struct 
        {
            if (it.HasValue)
                return selector(it.Value);
            else
                return null;
        }
    }


    public static class QFunc
    {
        public static Func<SqlCommand, TResult> Apply<T, TResult>(this T arg, Func<SqlCommand, T, TResult> func)
        {
            return c => func(c, arg);
        }        
    }

    public static class QExecuter
    {
        public static List<T> List<T>(this Func<SqlCommand, T> it)
        {
            return it.Result().ToList();
        }

        public static List<T> List<T>(this IQuery<Type<T>> it)
        {
            throw new NotImplementedException();
        }

        public static IEnumerable<T> Result<T>(this Func<SqlCommand, T> it)
        {
            using (var connection = new SqlConnection(Program.ConnectionString))
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    var prototype = it(command);
                    using (var reader = command.ExecuteReader())
                    {
                        var ordinals = reader.GetOrdinals(prototype);
                        while (reader.Read())
                            yield return ordinals.Materialize(reader);
                    }
                }
            }            
        }        
    }

    class Program
    {
        static void Main()
        {
            Example1(new DateTime(2014, 01, 02));
            Console.WriteLine("------------");
            Example1(null);
        }

        private static void Example1(DateTime? date)
        {
            var posts = Query<IPostInfo>.New(new {date}, @"
SELECT  Id, CreationDate
FROM    Post
WHERE   1 = 1
@if (date.HasValue) {
        @:AND CreationDate >= @date
}").List();
        }

        public static IPostInfo Example1Query(DateTime? date, SqlCommand command)
        {
            string.IsNullOrEmpty()
            var builder = new StringBuilder(@"
SELECT  Id, CreationDate
FROM    Post
WHERE   1 = 1");
            if (date.HasValue)
            {
                builder.Append(@"
        AND CreationDate >= @Date");
                command.Parameters.AddWithValue("@Date", date);
            }
            command.CommandText = builder.ToString();
            return default(IPostInfo);
        }

        public static string ConnectionString
        {
            get { return @"Data Source=(local)\SQL2012;Initial Catalog=CQExamples;Integrated Security=True"; }
        }
    }

    [TestClass]
    public class DALTest
    {
        [TestMethod]
        public void Example1Test()
        {
            MetadataTest(command => Program.Example1Query(null, command));
            MetadataTest(command => Program.Example1Query(new DateTime(2000, 1, 1), command));
        }

        private static void MetadataTest<T>(Func<SqlCommand, T> func)
        {
            using (var connection = new SqlConnection(Program.ConnectionString))
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    func(command);
                    using (var reader = command.ExecuteReader(CommandBehavior.SchemaOnly))
                    {
                        var expected = reader.GetSchemaTable().Rows.Cast<DataRow>().Select(row => new {
                            Name = (string) row["ColumnName"],
                            Type = GetType(row)
                        });
                        var actual = typeof (T).GetProperties().Select(info => new {
                            info.Name,
                            Type = info.PropertyType
                        });
                        if (expected.Except(actual).Any() || actual.Except(expected).Any())
                            throw new ApplicationException(@"Expected type:
public interface " + typeof(T).Name + @"
{
" + string.Join(@"
", expected.Select(_ => @"    " + _.Type.ToCSharp() + " " + _.Name + " { get; }")) + @"
}");
                    }
                }
            }
        }

        private static Type GetType(DataRow row)
        {
            var type = (Type) row["DataType"];
            return type.IsClass || false.Equals(row["AllowDBNull"])
                ? type
                : typeof (Nullable<>).MakeGenericType(type);
        }
    }

    public static class Util
    {
        public static string ToCSharp(this Type type)
        {
            var builder = new StringBuilder();
            using (var writer = new StringWriter(builder))
                CodeDomProvider.CreateProvider("C#").GenerateCodeFromExpression(
                    new CodeTypeReferenceExpression(type), writer, new CodeGeneratorOptions());
            return builder.ToString();
        }        
    }

    public interface IPostInfo
    {
        //int Id { get; }
        DateTime CreationDate { get; }
    }
}
